package base;

import static base.Edge.TURN_OFF;
import static base.Edge.TURN_ON;
import static com.google.common.base.Predicates.alwaysTrue;
import static com.google.common.collect.Iterables.concat;
import static com.google.common.collect.Iterables.filter;
import static com.google.common.collect.Iterables.transform;
import static com.google.common.collect.Sets.cartesianProduct;
import static com.google.common.collect.Sets.powerSet;

import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.Map.Entry;

import base.FlexibleComponent.Both;
import base.FlexibleComponent.Empty;
import base.FlexibleComponent.Marker;
import base.FlexibleComponent.OnlyOne;

import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.collect.Multimap;
import com.google.common.collect.Sets;

/**
 * Static helper methods for {@link FlexibleComponent}.
 * <p>
 * The method {@link #combine(Set, Set, Set, Predicate, Predicate, Predicate) combine( ... )} provides a one-call means to create a lazily-instantiated {@link Iterable}
 * of solutions.
 * <p>
 * TODO: Currently, {@link #combine(Set, Set, Set, Predicate, Predicate, Predicate) combine( ... )} considers all possible edge combinations.  However, it
 * should be possible to prune some edge combinations earlier by generating the base cases from the activation and inhibition equations.  That could prove a more
 * efficient approach.
 * @author Carl A. Pearson
 *
 */
public class FlexibleComponents {

	private FlexibleComponents() {}
	
	public static FlexibleComponent marker(FlexibleComponent lessThan) {
		assert lessThan.size() >= 0;
		return new Marker(lessThan.size()+1);
	}
	
	public static FlexibleComponent make(Set<Integer> inhibitors, Set<Integer> activators) {
		if (!Collections.disjoint(inhibitors, activators)) throw new IllegalArgumentException();
		return checkedMake(inhibitors,activators);		
	}
	
	public static FlexibleComponent make() {
		return FlexibleComponent.Empty.INSTANCE;
	}

	
	private static FlexibleComponent checkedMake(Set<Integer> inhibitors, Set<Integer> activators) {
		
		assert inhibitors != null;
		assert activators != null;
		assert Collections.disjoint(inhibitors, activators);
		
		if (inhibitors.isEmpty()) {
			return activators.isEmpty() ? Empty.INSTANCE : new OnlyOne(activators,TURN_ON);
		} else {
			return activators.isEmpty() ? new OnlyOne(inhibitors,TURN_OFF) : new Both(inhibitors,activators);
		}
	}
	
	
	/**
	 * This field can be used as a factory to create "base" {@code FlexibleComponent}s from {@link Set}s of known inhibitors and activators.
	 */
	private static final Function<List<? extends Set<Integer>>,FlexibleComponent> baseFactory =
		new Function<List<? extends Set<Integer>>,FlexibleComponent>() {
			@Override public FlexibleComponent apply(List<? extends Set<Integer>> from) {

				/* these assertions validate the checks in combine( ... ) */
				assert from != null;
				assert from.size() == 2;
				
				return checkedMake(from.get(0), from.get(1));
							
			}
		};
	
	/**
	 * Produces a lazy iterable of viable solutions.
	 * @param onlyInhibitors 
	 * @param onlyActivators
	 * @param mixed
	 * @param gSatFilter
	 * @param mixedFilter
	 * @return
	 */
	public static final Iterable<FlexibleComponent> combine(
		final Set<Integer> onlyInhibitors,
		final Set<Integer> onlyActivators,
		final Set<Integer> mixed,
		final Predicate<Set<Integer>> gSatFilter,
		final Predicate<FlexibleComponent> mixedFilter) {
		
		if (onlyInhibitors == null) throw new NullPointerException();
		if (onlyActivators == null) throw new NullPointerException();
		if (mixed == null) throw new NullPointerException();
		if (gSatFilter == null) throw new NullPointerException();
		
		if (
			!Collections.disjoint(onlyInhibitors, onlyActivators) &&
			!Collections.disjoint(onlyInhibitors, mixed) &&
			!Collections.disjoint(onlyActivators, mixed)) 
				throw new IllegalArgumentException();
		
		if (mixed.isEmpty()) 
			return filter(
					base(onlyInhibitors, onlyActivators, gSatFilter), 
					mixedFilter);
		
		return	filter(
					concat(
						transform(
							base(onlyInhibitors, onlyActivators, gSatFilter), 
							decorator(mixed)
						)
					),
				mixedFilter);
		
	}
	
	@SuppressWarnings("unchecked")
	private static final Iterable<FlexibleComponent> base(
			final Set<Integer> onlyInhibitors,
			final Set<Integer> onlyActivators,
			final Predicate<Set<Integer>> gSatFilter) {
		return transform(
				cartesianProduct(
					powerSet(onlyInhibitors),
					Sets.filter(
						powerSet(onlyActivators),
						gSatFilter)),
				baseFactory);
	}
	
	/**
	 * Translates the inhibition and inhibition + negative activation terms into a {@link Predicate}.
	 * <p>
	 * This predicate validates potential solutions.
	 * @param mixedTerms
	 * @param onlyInhibitionTerms
	 * @return
	 */
	public static final Predicate<FlexibleComponent> mixedPredicate(
		final Multimap<? extends Set<Integer>,Integer> mixedTerms,
		final Set<? extends Set<Integer>> onlyInhibitionTerms) {
		
		//mild optimization: if both the red/mixed parts are empty, they're always fine
		// if mixed is empty, then never need to fetch the green component from the solution
		if (mixedTerms.isEmpty()) 
			if (onlyInhibitionTerms.isEmpty()) {
				return alwaysTrue();
			} else return new Predicate<FlexibleComponent>() {
				@Override public boolean apply(FlexibleComponent solution) {
					Set<Integer> red = solution.get(TURN_OFF);	
					for (Set<Integer> term : onlyInhibitionTerms) if (Collections.disjoint(term, red)) return false;
					return true;
				}
			};
		
		return new Predicate<FlexibleComponent>() {
			@Override public boolean apply(FlexibleComponent solution) {
				
				Set<Integer> red = solution.get(TURN_OFF);
				
				for (Set<Integer> term : onlyInhibitionTerms) if (Collections.disjoint(term, red)) return false;
				
				Set<Integer> green = solution.get(TURN_ON);
								
				if (!green.isEmpty())
					for (Entry<? extends Set<Integer>,Collection<Integer>> term : mixedTerms.asMap().entrySet())
						if (Collections.disjoint(term.getKey(), red) && !Collections.disjoint(term.getValue(), green)) return false;
				
				return true;
				
			}
		};
	}
	
	/**
	 * 
	 * @param wholeExtras the set of edges that can be either activating or inhibiting
	 * @param gSatFilter {@code if(gSatFilter.apply( activatingEdges )) } then {@code activatingEdges } satisfies the activation equations.
	 * @return
	 */
	private static final Function<FlexibleComponent,Iterable<FlexibleComponent>> decorator(final Set<Integer> wholeExtras) {
		
		assert wholeExtras != null;
		assert !wholeExtras.isEmpty();
						
		return new Function<FlexibleComponent,Iterable<FlexibleComponent>>() {
			Set<Set<Integer>> power = powerSet(wholeExtras);

			@Override public Iterable<FlexibleComponent> apply(final FlexibleComponent base) {
				return concat(
						transform(
							power,
							base.appendFactory(wholeExtras)
						)
					);
			}
			
		};
		
	}
	
}
